using System;
using System.IO;

using Microsoft.SharePoint;

using Aks.IO.Extensions;
using Aks.Deployment.SharePoint;
using Aks.Deployment.SharePoint.Data;
using Aks.Deployment.SharePoint.Extensions;

namespace Aks.Deployment.Features
{
	/// <summary>
	/// uAKS site-scoped accessibility overrides feature receiver.
	/// </summary>
	public class SiteOverrideFeatureReceiver : SPFeatureReceiver
	{
		#region | Fields |

		private const string OriginalDefaultMasterVersionKey = AccessibilityOverrides.FeatureDirectory + "-default.master-version";
		private const string DefaultMasterPageUrl = "_catalogs/masterpage/default.master";
		private const string DefaultPageUrl = "default.aspx";
		private const string DefaultPageBackup = "uaks-default-backup.aspx";

		#endregion


		#region | Event Handlers |

		/// <summary>
		/// Occurs after a Feature is activated.
		/// </summary>
		/// <param name="properties">An <see cref="SPFeatureReceiverProperties"/> object that represents the properties of the event.</param>
		public override void FeatureActivated(SPFeatureReceiverProperties properties)
		{
			var web = SPFeatureReceiverPropertiesExtension.GetWeb(properties);
			ReplaceDefaultMasterPage(web);
			ReplaceDefaultPage(web);
		}


		/// <summary>
		/// Occurs when a Feature is deactivated.
		/// </summary>
		/// <param name="properties">An <see cref="SPFeatureReceiverProperties"/> object that represents the properties of the event.</param>
		public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
		{
			var web = SPFeatureReceiverPropertiesExtension.GetWeb(properties);
			RevertToDefaultMasterPage(web);
			RevertToDefaultPage(web);
		}


		/// <summary>
		/// Occurs after a Feature is installed.
		/// </summary>
		/// <param name="properties">An <see cref="SPFeatureReceiverProperties"/> object that represents the properties of the event.</param>
		public override void FeatureInstalled(SPFeatureReceiverProperties properties)
		{}


		/// <summary>
		/// Occurs when a Feature is uninstalled.
		/// </summary>
		/// <param name="properties">An <see cref="SPFeatureReceiverProperties"/> object that represents the properties of the event.</param>
		public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
		{}

		#endregion


		#region | Methods [Private] |

		#region | Master Page |

		/// <summary>
		/// Replaces the default master page.
		/// </summary>
		/// <param name="web">The <see cref="SPWeb"/> instance to replace the default.master for.</param>
		private static void ReplaceDefaultMasterPage(SPWeb web)
		{
			// get master page gallery and enable versioning
			var gallery = web.GetMasterPageGallery();
			gallery.EnableVersioning = true;

			// save current version number of default.master
			SaveListItemVersion(GetDefaultMasterPage(gallery), OriginalDefaultMasterVersionKey);

			// upload custom default.master
			CopyFileFromFeature(web, AccessibilityOverrides.FeatureDirectory, AccessibilityOverrides.DefaultMasterPage, DefaultMasterPageUrl);
		}


		/// <summary>
		/// Reverts to default master page.
		/// </summary>
		/// <param name="web">The <see cref="SPWeb"/> instance to revert to the original default.master for.</param>
		private static void RevertToDefaultMasterPage(SPWeb web)
		{
			// get master page gallery
			var gallery = web.GetMasterPageGallery();

			// get original default.master version number from site property bag
			var masterpage = GetDefaultMasterPage(gallery);
			if (masterpage == null)
			{
				return;
			}
			
			// restore the original version or delete if not used and no prior versions exist
			RestoreListItemVersion(masterpage, OriginalDefaultMasterVersionKey, !IsDefaultMasterPageUsed(web));
		}


		/// <summary>
		/// Gets the default master page.
		/// </summary>
		/// <param name="gallery">The master page gallery.</param>
		/// <returns>A list item representing the default master page.</returns>
		private static SPListItem GetDefaultMasterPage(SPList gallery)
		{
			var query = new SPQuery { Query = CamlWhereClause.FieldEqualToValue("FileLeafRef", "default.master") };
			var items = gallery.GetItems(query);
			return (items.Count == 0) ? null : items[0];
		}


		/// <summary>
		/// Determines whether the default master page is currently being used.
		/// </summary>
		/// <param name="web">The web.</param>
		/// <returns>
		/// 	<c>true</c> if the default master page is currently being used; otherwise, <c>false</c>.
		/// </returns>
		private static bool IsDefaultMasterPageUsed(SPWeb web)
		{
			const string url = "/" + DefaultMasterPageUrl;
			return (String.Compare(web.MasterUrl, url, StringComparison.OrdinalIgnoreCase) == 0
			        || String.Compare(web.CustomMasterUrl, url, StringComparison.OrdinalIgnoreCase) == 0);
		}

		#endregion


		#region | Default Page |

		/// <summary>
		/// Replaces the default page.
		/// </summary>
		/// <param name="web">The <see cref="SPWeb"/> instance to replace the default.aspx for.</param>
		private static void ReplaceDefaultPage(SPWeb web)
		{
			// TODO: Determine if this is a publishing site
			// TODO: Get welcome page

			// backup original file
			CopyFile(web, DefaultPageUrl, DefaultPageBackup);

			// overwrite default with accessible default
			switch (web.WebTemplateId)
			{
				case SiteTemplates.TeamSite.Id:
					CopyFileFromFeature(web, AccessibilityOverrides.FeatureDirectory, AccessibilityOverrides.TeamSiteDefaultPage, DefaultPageUrl);
					break;
			}
		}


		/// <summary>
		/// Reverts to default page.
		/// </summary>
		/// <param name="web">The <see cref="SPWeb"/> instance to revert to the original default.aspx for.</param>
		private static void RevertToDefaultPage(SPWeb web)
		{
			CopyFile(web, DefaultPageBackup, DefaultPageUrl, true);
		}

		#endregion


		#region | Copy |

		/// <summary>
		/// Copies the file from <paramref name="sourceUrl"/> to <paramref name="targetUrl"/>.
		/// </summary>
		/// <param name="web">The web.</param>
		/// <param name="sourceUrl">The feature-relative path to the source URL.</param>
		/// <param name="targetUrl">The site-relative target URL.</param>
		private static void CopyFile(SPWeb web, string sourceUrl, string targetUrl)
		{
			CopyFile(web, sourceUrl, targetUrl, false);
		}


		/// <summary>
		/// Copies the file from <paramref name="sourceUrl"/> to <paramref name="targetUrl"/>.
		/// </summary>
		/// <param name="web">The web.</param>
		/// <param name="sourceUrl">The feature-relative path to the source URL.</param>
		/// <param name="targetUrl">The site-relative target URL.</param>
		/// <param name="deleteSource">If set to <c>true</c>, the source file is deleted after copied.</param>
		private static void CopyFile(SPWeb web, string sourceUrl, string targetUrl, bool deleteSource)
		{
			// get source file
			var source = web.Files[sourceUrl];
			if (source == null)
			{
				return;
			}

			// copy to target url
			using (var stream = source.OpenBinaryStream(SPOpenBinaryOptions.SkipVirusScan))
			{
				web.Files.Add(targetUrl, stream, true);
			}

			// delete source file, if necessary
			if (deleteSource)
			{
				source.Delete();
			}
		}


		/// <summary>
		/// Copies the specified <paramref name="sourceFile"/> from the specified <paramref name="feature"/> directory to <see cref="targetUrl"/>.
		/// </summary>
		/// <param name="web">The web.</param>
		/// <param name="feature">The name of the feature directory.</param>
		/// <param name="sourceFile">The feature-relative path to the source file.</param>
		/// <param name="targetUrl">The site-relative target URL.</param>
		private static void CopyFileFromFeature(SPWeb web, string feature, string sourceFile, string targetUrl)
		{
			using (var stream = File.OpenRead(PathExtension.Combine(SetupPath.Features, feature, sourceFile)))
			{
				web.Files.Add(targetUrl, stream, true);
			}
		}

		#endregion


		#region | Versioning |

		/// <summary>
		/// Saves the current version id for the specified <paramref name="item"/> to the <see cref="SPWeb.AllProperties"/> 
		/// property bag using the specified <paramref name="versionKey"/>.
		/// </summary>
		/// <param name="item">The list item.</param>
		/// <param name="versionKey">The version key.</param>
		private static void SaveListItemVersion(SPListItem item, string versionKey)
		{
			// validate params
			if (String.IsNullOrEmpty(versionKey))
			{
				throw new ArgumentNullException("versionKey", "A version key is required to save the file version.");
			}
			if (item == null || item.Versions.Count == 0)
			{
				return;
			}

			// get the current version and add it to the web property bag
			var web = item.Web;
			var version = item.Versions[item.Versions.Count - 1];
			var properties = web.AllProperties;
			if (properties.ContainsKey(versionKey))
			{
				properties.Remove(versionKey);
			}
			properties.Add(versionKey, version.VersionId);
			web.Update();
		}


		/// <summary>
		/// Restores a version of the specified <paramref name="item"/> based on the version id saved to the 
		/// <see cref="SPWeb.AllProperties"/> property bag using the specified <paramref name="versionKey"/>.
		/// </summary>
		/// <param name="item">The lite item.</param>
		/// <param name="versionKey">The version key.</param>
		/// <param name="deleteIfNoPriorVersions">If set to <c>true</c>, deletes the list item if there are no prior versions.</param>
		private static void RestoreListItemVersion(SPListItem item, string versionKey, bool deleteIfNoPriorVersions)
		{
			// validate params
			if (item == null || String.IsNullOrEmpty(versionKey) || !item.Web.AllProperties.ContainsKey(versionKey))
			{
				return;
			}

			// get version and delete property -- must save collection as local var to actually apply the removal
			var properties = item.Web.AllProperties;
			var versionId = (int)(properties[versionKey] ?? -1);
			properties.Remove(versionKey);
			item.Web.Update();

			// if only one version and no restore version was saved, delete the file; otherwise, restore
			if (item.Versions.Count == 1 && versionId > 0 && deleteIfNoPriorVersions)
			{
				item.Delete();
			}
			else if (item.Versions.Count > 1)
			{
				try
				{
					// TODO: This isn't actually restoring the correct version
					item.Versions.RestoreByID(versionId);
				}
				catch (ArgumentOutOfRangeException)
				{
					/**
					 * Source: SPListItemVersionCollection.RestoreByID()
					 * Reason: Version id is no longer valid
					 * Action: Ignore
					 */
				}
			}
		}

		#endregion

		#endregion
	}
}